/*
 * VmMap.cpp
 *
 *  Created on: 10.04.2010
 *      Author: artur
 */

#include <kiddie/kiddie.h>
#include <platform.h>
#include <sys/errno.h>

#include <Console.h>
#include <hw/Platform.h>
#include <mem/MemTypes.h>
#include <mem/VmMap.h>

extern kernel::Console cout;
extern kernel::Platform hwplatform;

namespace kernel
{
namespace mem
{
int
VmMap::mergeArea(MemArea *area)
{
    MemArea *_area;
    vaddr_t addr_end;
    int ret;
    ENTER;
    OUTDEBUG("[ VmMap ]");
    ret = 0;
    for (_area = this->usedareas.next; _area && (_area != &this->usedareas); _area
         = _area->next)
    {
        if (area == _area)
        {
            continue;
        }
        if (area->v_addr >= _area->v_addr && area->v_addr <= (_area->v_addr
                                                              + _area->length))
        {
            addr_end = ((area->v_addr + area->length) > (_area->v_addr
                                                         + _area->length)) ? (area->v_addr + area->length)
                                                                           : (_area->v_addr + _area->length);
            _area->length = addr_end - _area->v_addr;
            ret = 1;
            break;
        }
    }

    EXIT;
    return ret;
}

int
VmMap::mergeAllAreas(void)
{
    int run;
    MemArea *area, *tmp_area;
    ENTER;
    OUTDEBUG("[ VmMap ]");
    for (area = this->usedareas.next; area && area != &this->usedareas;)
    {
        tmp_area = NULL;
        if (mergeArea(area))
        {
            /* remove area from list */
            area->prev->next = area->next;
            area->next->prev = area->prev;
            tmp_area = area;
        }
        area = area->next;
        // if (NULL != tmp_area) kfree(tmp_area);
    }
    EXIT;
    return 0;
}

/**
     *
     */
vaddr_t
VmMap::getFreeVAddr(paddr_t paddr, size_t siz)
{
    MemArea *_area, *tmp_area;
    size_t len, off = paddr % PAGE_SIZE;
    vaddr_t vaddr;
    OUTDEBUG("[ VmMap ]");
    len = PAGE_ALIGN(off + siz);

    _area = &this->freeareas;
    do
    {
        if (len < _area->length)
        {
            vaddr = (vaddr_t) PAGE_TRUNC(_area->v_addr);
            _area->v_addr += len;
            _area->length -= len;
            _area->length = PAGE_TRUNC(_area->length);
            return vaddr;
        }
        _area = _area->next;
    }
    while (_area != &this->freeareas);

    ::cout << "getFree " << (int) ((len)) << "," << (int) (vaddr) << "\n";
    return NULL;
}

/**
     *
     */
vaddr_t
VmMap::remap(paddr_t paddr, vaddr_t _vaddr, size_t siz)
{
    size_t off = paddr % PAGE_SIZE;
    int num = PAGE_ALIGN(off + siz) / PAGE_SIZE;

    // OUTDEBUG("[ VmMap ]");
    cout << kernel::Console::HEX << "[ VmMap ] remapping " << num
         << " pages from paddr(0x" << (int) (PAGE_TRUNC(paddr))
         << ")  to vaddr(0x" << (int) (PAGE_TRUNC(_vaddr)) << ")"
         << kernel::Console::DEC << (int) (PAGE_ALIGN(off + siz)) << "]\n";

    kernel::mem::MemArea area(PAGE_TRUNC(paddr), (PAGE_TRUNC(_vaddr)),
                              PAGE_ALIGN(off + siz), false);
    ArchVmMap::archMapArea(const_cast<kernel::mem::MemArea &>(area));

    return _vaddr + off;
}

/**
     *
     */
vaddr_t
VmMap::remap(paddr_t paddr, size_t siz)
{
    //OUTDEBUG("[ VmMap ]");
    vaddr_t vaddr = this->getFreeVAddr(paddr, siz);
    cout << kernel::Console::HEX
         << "[ VmMap ] VmMap::remap() remapping paddr(0x" << (int) (paddr)
         << ") vaddr(0x" << (int) (vaddr) << ") SIZ[" << kernel::Console::DEC
         << (int) (siz) << "]\n";
    vaddr = remap(paddr, vaddr, siz);
    return vaddr;
}

vaddr_t
VmMap::mapPage(vaddr_t vaddr)
{
    MemArea area;
    area.length = PAGE_SIZE;
    area.v_addr = vaddr;
    area.alloc = false;
    ArchVmMap::archMapArea(const_cast<kernel::mem::MemArea &>(area));
    *this += area;
}

/**
     *
     */
vaddr_t
VmMap::mapArea(void)
{
    kernel::mem::MemArea *area;
    for (area = this->usedareas.next; area && (area != &this->usedareas); area
         = area->next)
    {
        if (area->length > 0)
            ArchVmMap::archMapArea(const_cast<kernel::mem::MemArea &>(*area));
    }
}

/**
     *
     */
VmMap &
VmMap::operator +=(const MemArea & blk)
{
    MemArea *area;
    vaddr_t addr_start, addr_end;
    ENTER;
    if (NULL == blk.v_addr || blk.length <= 0)
    {
        ::cout << "[MemMap]  ERROR!! Invalid parameters\n";
        EXIT;
        goto plusout;
    }
    addr_start = PAGE_TRUNC(blk.v_addr);
    addr_end = PAGE_ALIGN(blk.v_addr + blk.length);
    if (addr_end >= (vaddr_t) & KERNEL_BASE)
    {
        ::cout << "[vm] ERROR!! Invalid mem area\n";
        EXIT;
        goto plusout;
    }
    for (area = this->usedareas.next; area && (area != &this->usedareas); area
         = area->next)
    {
        // OUTDEBUG("[ VmMap ]");
        /* find overlapped area */
        if (addr_start >= area->v_addr && addr_start <= (area->v_addr
                                                         + area->length))
        {
            // OUTDEBUG("[ VmMap ]");
            /* create union  */
            addr_end = (addr_end > (area->v_addr + area->length)) ? addr_end
                                                                  : (area->v_addr + area->length);
            area->length = addr_end - area->v_addr;
            ::cout << kernel::Console::HEX << "[vm] New area: "
                      "area->v_addr=0x" << (int) area->v_addr << ", area->length=0x"
                   << (int) area->length << "\n" << kernel::Console::HEX;
            EXIT;
            goto plusout;
        }
    }
    area = new MemArea();
    if (!area)
    {
        ::cout << "[vm] ERROR! No mem space\n";
        EXIT;
        goto plusout;
    }
    area->length = addr_end - addr_start;
    area->v_addr = addr_start;
    area->prev = this->usedareas.prev;
    area->next = &this->usedareas;
    area->prev->next = area;
    area->alloc = blk.alloc;
    area->p_addr = blk.p_addr;
    this->usedareas.prev = area;
    EXIT;
plusout: return *this;
}

/**
     *
     */
VmMap &
VmMap::operator -=(const MemArea & blk)
{
    return *this;
}

/**
     *
     */
VmMap &
VmMap::operator =(VmMap & _from)
{
    _init();
    return *this;
}

VmMap::VmMap()
{
    OUTDEBUG("[ VmMap ] Constructor ");
    _init();
}

VmMap::VmMap(kernel::mem::page::PageFactory *pNode)
{
    OUTDEBUG("[ VmMap ] Constructor ");
    ArchVmMap::setPageFactory(pNode);
    _init();
}

VmMap::VmMap(kernel::mem::page::PageFactory *pNode, pgd_t _pgd)
{
    OUTDEBUG("[ VmMap ] Constructor ");
    ArchVmMap::setPageFactory(pNode);
    ArchVmMap::setPgd(_pgd);
    _init();
}

/**
     *
     */
VmMap::~VmMap()
{
}

/**
     *
     */
void
VmMap::_init(void)
{
    usedareas.next = &usedareas;
    usedareas.prev = &usedareas;
    usedareas.p_addr = 1;
    usedareas.v_addr = 1;
    usedareas.length = 0;

    freeareas.next = &freeareas;
    freeareas.prev = &freeareas;
    freeareas.p_addr = 0xb0000000;
    freeareas.v_addr = 0xb0000000;
    freeareas.length = 0x10000000;
}
} // mem
} // kernel
